# -*- coding: utf-8 -*-

import re

class Cave():
    """Cave grid, file parsing, validation and stats."""
    description = []
    
    max_width, max_hight = 100, 100
    MN,MS,MW,ME = None, None, None, None

    @staticmethod
    def N(r): return (r[0],r[1]-1)
    @staticmethod
    def E(r): return (r[0]+1,r[1])
    @staticmethod
    def S(r): return (r[0],r[1]+1)
    @staticmethod
    def W(r): return (r[0]-1,r[1])
    
    grid = {}  # (x,y) -> set('P','B10','E','W','S','G')
    bats = set()

    def __init__(self, grid=''):
        """
            Parses grid from text.

            Wumpus cave map file format (.cave)
            
            Lines starting with # are comments.
            Empty lines and spaces are ignored.
            Other lines contain information about rows of rooms in the cave.
            Room is represented by string with it content and 
            starts and ends with | as separator. 
            Posible room content:
                P - pit
                B<n> - bats where n is an integer showing 
                G - gold
                E - entrance
                W - wumpus
                S - stone (used to design irregular caves)
                
            Room rows can have different lengths. 
            Shorter rows will be filled with stones.
            
            Examples
            
            Single empty room | |
            Two rooms one with entrance, second with gold.  |E|G|
        """
        self.MN, self.MS, self.MW, self.ME = 0,0,0,0
        self.description = []
        self.grid = {}
        
        for ln,line in enumerate(grid.strip().split('\n')):
            line = line.strip()
            if not line: continue
            if line[0] == '#':
                self.description.append(line[1:])
            else:
                pi = re.compile('(W|P|B\d+|E|G|S)')
                rooms = line.split('|')[1:-1]  #strip empty space befor first and after last bar |
                for rn, room in enumerate(rooms):
                    self.grid[rn,self.MS] = set()
                    for item in pi.split(room):
                        if not item.strip(): continue
                        if item[0] not in 'WPBEGS': 
                            print 'Line %d, Room %d: unknown items %s' % (ln, rn, item)
                        else:
                            if item[0] == 'B': 
                                self.bats.add(item)
                                self.grid[rn,self.MS].add('B')
                            self.grid[rn,self.MS].add(item)
                    if 'S' in self.grid[rn,self.MS]:
                        if len(self.grid[rn,self.MS]) > 1:
                            print 'Line %d, Room %d: Items in the stone detected (%s).' % (ln, rn, room)
                        del self[rn, self.MS]
                if rn >= self.ME: self.ME = rn+1
                self.MS += 1

    def _calc_size(self):
        x,y = [], []
        for r in self.grid.keys():
            x.append(r[0])
            y.append(r[1])
        self.MN, self.MS, self.MW, self.ME = min(y), max(y), min(x), max(x)
            
    def __str__(self):
        self._calc_size()
        s = ''.join(['#%s\n' % d for d in self.description])
        BS = set('B')
        for y in range(self.MN, self.MS+1):
            rooms = []
            for x in range(self.MW, self.ME+1):
                rooms.append(''.join(self[x,y]-BS))            
            s += '|' + '|'.join(rooms) + '|\n'
        return s
    def validate(self):
        """Validates cave"""
        warnings = []
        errors = []
        info = []

        r = {}
        for t in ['E', 'W', 'G', 'P', 'B']:
            r[t] = self.roomsWith(t)

        #no/too many entrance?
        if not r['E']:
            errors.append('No entrance.')
        if len(r['E']) > 1:
            warnings.append('More than one entrance (%d).' % len(r['E']))
        
        #entrance in deadly place?
        if r['E'] & (r['P'] | r['W']):
            warnings.append('Entrance in a deadly place.')
        
        if len(r['W']) > 1:
            warnings.append('More than one wumpus (%d).' % len(r['W']))
        #no gold?
        if not r['G']:
            warnings.append('No gold.')

        #gold in a pit?
        if r['G'] & r['P']: 
            warnings.append('Gold in the pit.')

        #unpaired bats?
        single = []
        multiple = []
        for t in self.bats:
            rb = self.roomsWith(t)
            if len(rb) == 1: single.append(t)
            elif len(rb) > 2: multiple.append(t)
        if single:
            errors.append('Unpaired bat rooms (%s).' % (', '.join(single)))
        if multiple:
            warnings.append('Bat rooms with multiple exits (%s).' % (', '.join(multiple)))

        #bats at deadly place?
        if r['B'] & (r['P'] | r['W']): 
            warnings.append('Bats leading to deadly place.')
        return errors, warnings

    def stats(self):
        """Calculates information about cave"""
        pass
    
    #container interface
    def __len__(self):
        return len(self.grid)
    def __getitem__(self, key):
        return self.grid[key] if self.grid.has_key(key) else set('S')
    def __setitem__(self, key, value):
        # TODO check value
        self.grid[key] = value
    def __delitem__(self, key):
        del self.grid[key]
    def __iter__(self):
        return iterkeys(self.grid)
    def __contains__(self, item):
        return item in self.grid
    def rooms(self, withall=None, withsome=None, withitem=None):
      for k,r in self.grid.iteritems():
        if withall and not withall <= r: continue
        if withsome and not withsome & r: continue
        if withitem and not withitem in r: continue
        yield k
    def roomsWith(self,item):
        return set(r for r in self.rooms(withitem=item))

if __name__ == "__main__":
    print 'TODO: add tests here'

